#include "Game.h"

using eastl::string;

const Vec HALF_TILE_SIZE = Vec(TILE_SIZE, TILE_SIZE) / 2.0f;

CCreepData CreepData[ALL_CREEPS] = {
	{ CREEP_NORMAL, "normal", 100.0f, 100.0f, 10 },
	{ CREEP_FLYER, "flyer", 250.0f, 50.0f, 20 },
	{ CREEP_TANK, "tank", 50.0f, 200.0f, 20 }
};

eastl::map<eastl::string, int> CreepTypesByName(CreepData, CreepData+sizeof(CreepData)/sizeof(CreepData[0]));


struct CTowerType
{
	size_t Cost;
	float Range;
	float Damage;
	float SpeedMultiplier;
} TowerTypes[ALL_TOWERS] = {
	{ 10, 100.0f, 10.0f, 2.0f },
	{ 20, 70.0f, 5.0f, 1.0f },
	{ 40, 120.0f, 2.0f, 3.0f },
	{ 70, 50.0f, 20.0f, 4.0f },
};

/// Game

//static const double CreepSpawnInterval = 0.5;

double BeatPercent = 0;
double TaktPercent = 0;

void Game::Update(float dt)
{
	BeatPercent = fmod(TotalGameTime, BEAT) / BEAT;
	TaktPercent = fmod(TotalGameTime / TAKT, BEAT) / BEAT;

	World::Update(dt);

	/// Waves

	if (!LevelComplete)
	{
		if (WaveState == WAVE_SPAWNING)
		{
			if (TotalGameTime - LastWaveCommandTime > BEAT)
			{
				LastWaveCommandTime = TotalGameTime;
				SpawnCreep();
			}
		}
		else if (WaveState == WAVE_BREATH_BETWEEN_WAVES)
		{
			NextWaveTime -= dt;
			if (NextWaveTime <= 0)
			{
				NextWaveTime = 0;
				WaveState = WAVE_SPAWNING;
				AnimEvent("startwave", Prop());
			}
		} else if (WaveState == WAVE_WAITING_FOR_DEAD)
		{
			size_t c = CountObjects('Crep');
			if (!c)
			{
				WaveState = WAVE_BREATH_BETWEEN_WAVES;
				NextWaveTime = 10.0;
			}
		}
	}

	/// Update GUI

	if (ShowHUD)
	{
		if (HUDAnim > DestHUDAnim)
		{
			HUDAnim -= dt;
			if (HUDAnim < DestHUDAnim)
				HUDAnim = DestHUDAnim;
		} else if (HUDAnim < DestHUDAnim)
		{
			HUDAnim += dt;
			if (HUDAnim > DestHUDAnim)
				HUDAnim = DestHUDAnim;
		}
	}

	if (WasPressed(MOUSE_RIGHT))
	{
		PlaySnd(SndNo);
		ShowHUD = false;
	}

	/// Display vars
	Money.Update(dt);

	//this->RenderOffset = Vec(cos(TotalGameTime*10), sin(TotalGameTime*10)) * 32.0f;
}

void Game::SpawnCreep()
{
	CreepCommand& command = CurrentLevel.Waves[CurrentWave].Commands[CurrentWaveCommand];
	if (command.Count)
	{
		command.Count--;
		SpawnCreep(command.CreepType);
	}
	else
	{
		AnimEvent("endpack", Prop());
		CurrentWaveCommand++;
		if (CurrentWaveCommand == CurrentLevel.Waves[CurrentWave].Commands.size())
		{
			AnimEvent("endwave", Prop());
			CurrentWaveCommand = 0;
			CurrentWave++;
			if (CurrentWave == CurrentLevel.Waves.size())
			{
				LevelComplete = true;
				AnimEvent("leveldone", Prop());
			}
			WaveState = WAVE_WAITING_FOR_DEAD;
		}
	}
}

void Game::SpawnCreep(int type)
{
	Creep* cp = new Creep;
	cp->InitType(type);
	cp->Pos = CoordToCenter(CurrentLevel.SpawnPos);
	cp->Rot = cp->RealRot = cp->Pos.DirTo(CurrentLevel.Path[0]).Angle();
	cp->AnimEvent("spawn", cp->Pos);
	AddToWorld(cp);
}

void Game::InitGameplay()
{
	LoadLevel("Data/level.txt");

	CurrentWave = 0;
	CurrentWaveCommand = 0;
	LastWaveCommandTime = 0.0;

	LevelComplete = false;

	ShowHUD = false;
	HUDAnim = DestHUDAnim = 0;
	StartHUDAngle = 0.0f;

	SelectedObj = 0;

	//Money = TowerTypes[TOWER_BEAT_RIFLE].Cost*2;
	Money.Set(10000);

	WaveState = WAVE_BREATH_BETWEEN_WAVES;
	NextWaveTime = 4.0;

	SndShot = LoadSnd("Data/shot.wav");
	SndClick = LoadSnd("Data/click.wav");
	SndNo = LoadSnd("Data/no.wav");
	SndBlip = LoadSnd("Data/blip.wav");
}

void Game::LoadLevel(const char* filename)
{
	KillAll();
	CurrentLevel.LoadFromFile(filename);
	for (size_t i=0; i<CurrentLevel.Tiles.elements.size(); i++)
		AddToWorld(CurrentLevel.Tiles.elements[i]);
}

void Game::GameEvent(const char* event, const Prop& arg)
{
}

#define ALLEGRO_EXTERN extern "C" __declspec(dllimport)

ALLEGRO_EXTERN void al_calculate_arc(float* dest, int stride, float cx, float cy,
   float rx, float ry, float start_theta, float delta_theta, float thickness,
   int num_segments);

void Game::RenderGUI()
{
	if (!ShowHUD)
		return;

	Vec hud_pos = Vec(max(40, MenuPos.X), max(40, MenuPos.Y));

	size_t buttons = CurrentMenu.Buttons.size();
	
	eastl::vector<Vec> positions;
	positions.resize(buttons);

	static const float MENU_RADIUS = 80.0f;
	static const float MENU_RADIUS_MULT = MENU_RADIUS*1.4f;

	float anim = Ease(EASING_OUTBOUNCE, HUDAnim, 1.0f, 0.0f, MENU_RADIUS) + 8 * sin(fmod(TotalGameTime, BEAT));

	float starthud = StartHUDAngle;
	if (hud_pos.Y < MENU_RADIUS_MULT && hud_pos.X < MENU_RADIUS_MULT)
		starthud += DegToRad(180.0f);
	else if (hud_pos.X < MENU_RADIUS_MULT)
		starthud += DegToRad(90.0f);
	else if (hud_pos.Y < MENU_RADIUS_MULT)
		starthud += DegToRad(180.0f);
	else if (hud_pos.X > SCREEN_WIDTH-MENU_RADIUS_MULT)
		starthud += DegToRad(270.0f);

	al_calculate_arc(
		&positions[0].X, 
		sizeof(Vec), 
		0,
		0,
		anim, 
		anim, 
		starthud, 
		DegToRad(90.0f), 
		0, 
		(int)buttons);

	for (size_t i=0; i<buttons; i++)
	{
		int flags = 0;
		if (!CurrentMenu.Buttons[i].Enabled)
			flags = GUIFLAG_Disabled;

		if (CurrentMenu.Buttons[i].Draw('MENU'+int(i), positions[i]+hud_pos, flags))
		{
			if (CurrentMenu.Buttons[i].Enabled)
			{
				PlaySnd(SndClick, 1.0f, false);
				if (CurrentMenu.Buttons[i].ClickFunc)
					CurrentMenu.Buttons[i].ClickFunc(this, &CurrentMenu.Buttons[i]);
			}
			else
			{
				PlaySnd(SndNo);
			}
		} 
	}

	int uid = GetGUIHotItem();
	if (uid)
	{
		uid -= 'MENU';
		DrawFntCenter(FontSmall, positions[uid].X+hud_pos.X, positions[uid].Y+hud_pos.Y+TILE_SIZE-20, Color::White(), "%s", CurrentMenu.Buttons[uid].Description.c_str());
		Vec tower_pos = CoordToCenter(PosToCoord(MenuPos));
		Tower::DrawRange(tower_pos, TowerTypes[uid].Range);
		Tower::DrawPreview(uid, tower_pos);
	}
}

void BuildTower(Game* game, Game::Menu::MenuButton* button)
{
	Vec pos = PosToCoord(game->MenuPos);
	game->BuildTower(pos, button->Data.Number);
	game->ShowHUD = false;
}

void Game::BuildTower(Vec at, int type)
{
	Tower* cp = new Tower;
	cp->InitType(type);
	cp->Pos = CoordToCenter(at);
	cp->AnimEvent("build", cp->Pos);
	AddToWorld(cp);

	Money.Value -= TowerTypes[type].Cost;
}

void Game::ShowMenu(Vec at)
{
	ShowHUD = true;
	HUDAnim = 0.21f; 
	DestHUDAnim = 1.0f;
	StartHUDAngle =	DegToRad(180);
	MenuPos = at;

	CurrentMenu.Buttons.resize(4);
	CurrentMenu.Buttons[0].Description = "Build Beat Rifle";
	CurrentMenu.Buttons[1].Description = "Build Chipstep Coil";
	CurrentMenu.Buttons[2].Description = "Build Snare Gun";
	CurrentMenu.Buttons[3].Description = "Build Bass Cannon";
	
	for (int i=0; i<ALL_TOWERS; i++)
	{
		CurrentMenu.Buttons[i].ClickFunc = ::BuildTower;
		CurrentMenu.Buttons[i].Data.Number = i;
		CurrentMenu.Buttons[i].Enabled = (TowerTypes[i].Cost <= Money.Value);
	}
}

int Game::Menu::MenuButton::Draw(int uid, Vec at, int flags)
{
	return Button(uid, at.X-24, at.Y-24, Image.c_str(), 36, 36, Color::Gray(0.75f), flags);
}

/// Tile

void Tile::Event(int type, const Prop& arg)
{
	CDObj::Event(type, arg);
	if (type == MEV_CLICK)
	{
		Select();
		if (TileType == TILE_GRASS)
		{
			PlaySnd(GetGame().SndClick, 1.0f, false);
			GetGame().ShowMenu(arg.Position+Pos);
		}
		else
		{
			GetGame().ShowHUD = false;
			PlaySnd(GetGame().SndNo);
		}
	}
}

/// CDObj

void CDObj::Event(int event, const Prop& arg)
{
	if (event == MEV_OVER && !GetGame().ShowHUD)
	{
		Select();
	}
}

/// Level

void DeleteTile(Tile*& obj)
{
	delete obj;
	obj = 0;
}

void Level::LoadFromFile(const char* filename)
{
	Tiles.MapElements(DeleteTile);

	FileReader reader;
	reader.Open(filename);

	int w = reader.ReadInt();
	int h = reader.ReadInt();

	Tiles.Resize(w,h);
	Path.clear();
	Vec base;

	for (int i=0; i<h; i++)
	{
		string row = reader.ReadLine();
		while (row.size() < (size_t)w)
			row = reader.ReadLine();

		for (int c=0; c<w; c++)
		{
			char tile = row[c];
			Tile* tileobj = new Tile;
			switch (tile)
			{
			case ' ':
				tileobj->TileType = TILE_VOID;
				break;
			case '.':
				tileobj->TileType = TILE_GRASS;
				break;
			case '#':
				tileobj->TileType = TILE_ROAD;
				break;
			case 'S':
				tileobj->TileType = TILE_SPAWN;
				SpawnPos = Vec(c, i);
				break;
			case 'B':
				base = CoordToCenter(Vec(c, i));
				tileobj->TileType = TILE_BASE;
				break;
			}
			tileobj->X = c;
			tileobj->Y = i;
			tileobj->Pos = Vec(c*TILE_SIZE, i*TILE_SIZE);
			Tiles(c, i) = tileobj;

			if (tile >= 'a' && tile <= 'z')
			{
				int pathpoint = (tile-'a');
				Path.resize(max(Path.size(), pathpoint+1));
				Path[pathpoint] = CoordToCenter(Vec(c, i));

				tileobj->TileType = TILE_ROAD;
			}
		}
	}

	Path.push_back(base);
	
	/*
	int path_size = reader.ReadInt();
	for (int i=0; i<path_size; i++)
	{
		Path.push_back(CoordToCenter(Vec(reader.ReadInt(), reader.ReadInt())));
	}
	*/

	int wave_count = reader.ReadInt();
	Waves.resize(wave_count);
	for (int w=0; w<wave_count; w++)
	{
		int command_count = reader.ReadInt();
		Waves[w].Commands.resize(command_count);
		for (int c=0; c<command_count; c++)
		{
			string type = reader.ReadString();
			size_t count = reader.ReadInt();
			Waves[w].Commands[c].CreepType = CreepTypesByName[type];
			Waves[w].Commands[c].Count = count;
		}
	}

	reader.Close();
}

/// Creep

void Creep::Update(float dt)
{
	CDObj::Update(dt);

	Vec next_point = GetLevel().Path[NextPathPoint];
	Vec dir = Pos.DirTo(next_point);

	Rot = dir.Angle();

	float actual_speed = Speed;
	if (GetTimer(EFFECT_SLOWED) > 0.0)
		actual_speed *= 0.5f;

	Pos += dir * actual_speed * dt;

	float dist = Pos.DistanceSquared(next_point);
	if (dist < 10.0f)
	{
		Pos = next_point;
		if (NextPathPoint+1 == (int)GetLevel().Path.size())
		{
			AnimEvent("base", Prop());
		}
		else
		{
			NextPathPoint++;
			AnimEvent("turn", GetLevel().Path[NextPathPoint]);
		}
	}
}

void Creep::Event(int event, const Prop& arg)
{
	CDObj::Event(event, arg);
	if (event == EV_TIMER && arg.Number == EFFECT_SLOWED)
	{
		AnimEvent("slowed end", Prop());
	}
	else if (event == 'kill')
	{
		GetGame().Money.Value += CreepData[CreepType].Money;
		Dead = true;
	}
}

void Creep::InitType(int type)
{
	CreepType = type;
	Health = CreepData[type].Life;
	Speed = CreepData[type].Speed;
}

/*
TOWER_BEAT_RIFLE,
TOWER_CHIPSTEP_COIL,
TOWER_SNARE_GUN,
TOWER_BASE_CANNON,
*/

void Creep::Damage(float damage)
{
	Health -= damage;
	AnimEvent("damage", damage);
	if (Health <= 0.0f)
	{
		AnimEvent("kill", -Health);
		Health = 0.0f;
	}
}

void Creep::TowerHit(Tower* tower)
{
	if (Dead)
		return;

	float tower_power = tower->Power; /// :)
	float damage = tower->Power * tower->Damage;

	//printf("Tower %u hit creep %s for %g damage!\n", tower->TowerType, CreepData[CreepType].Name, damage);

	Creep* next = 0;
	switch (tower->TowerType)
	{
	case TOWER_BEAT_RIFLE:
		Damage(damage);
		break;
	case TOWER_CHIPSTEP_COIL:
		Damage(damage);
		next = (Creep*)FindClosest('Crep', TILE_SIZE*2, (bool(*)(void*))0);
		if (next)
		{
			AnimEvent("spark", Prop(next));
			next->Damage(damage);
		}
		break;
	case TOWER_SNARE_GUN:
		if (GetTimer(EFFECT_SLOWED) > 0.0)
			AnimEvent("slowed again", Prop(5.0*tower_power));
		else
			AnimEvent("slowed start", Prop(5.0*tower_power));
		SetTimer(EFFECT_SLOWED, 5.0*tower_power);
		Damage(damage);
		break;
	case TOWER_BASE_CANNON:
		Damage(damage);
		break;
	}
}

/// Tower

void Tower::Update(float dt)
{
	CDObj::Update(dt);
	if (Life-LastShot >= BEAT*SpeedMultiplier)
	{
		LastShot = Life;
		Fire();
	}
}

struct FindTargetForTower
{
	Tower* tower;
	FindTargetForTower(Tower* t) : tower(t) {}
	bool operator() (Obj* obj) 
	{
		Creep* creep = (Creep*)obj;
		if (creep->Health <= 0.0f)
			return false;
		if (tower->TowerType == TOWER_SNARE_GUN && creep->GetTimer(EFFECT_SLOWED) > 0.0)
			return false;
		return true;
	}
	operator bool() { return true; }
};

void Tower::Fire()
{
	Creep* closest = (Creep*)FindClosest('Crep', this->Range, FindTargetForTower(this));

	if (closest)
	{
		this->AnimEvent("fire", Prop(closest));
		PlaySnd(GetGame().SndShot, 1.0f, false);
		closest->TowerHit(this);
	}
}

void Tower::InitType(int type)
{
	TowerType = type;
	Range = TowerTypes[type].Range;
	Damage = TowerTypes[type].Damage;
	Power = 1.0f;
	SpeedMultiplier = TowerTypes[type].SpeedMultiplier;
}

void Tower::Event(int event, const Prop& arg)
{
	CDObj::Event(event, arg);
}